<?php
class Ws{
	private $host = '0.0.0.0';
	private $port = 7000;
	private $maxuser = 10000;
	public  $accept = array(); //连接的客户端
	private $cycle = array(); //循环连接池
	private $isHand = array();

    private static $connections;
    private static $chats;
    private static $auth;
    private static $heartbeats;
    private static $uid;
	/*
		接受三个回调函数，分别在新用户连接、有消息到达、用户断开时触发
		function add、function send、function close
	*/
	public $function = array();
	//Constructor
	function __construct($host, $port, $max) {
		$this->host = $host;
		$this->port = $port;
		$this->maxuser = $max;
	}
	//挂起socket
	public function start_server() {
		$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
		//允许使用本地地址
		socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, TRUE); 
		socket_bind($this->socket, $this->host, $this->port);
		//
		socket_listen($this->socket, $this->maxuser); 
		while(TRUE) {
			$this->cycle = $this->accept;
			$this->cycle[] = $this->socket;
			//阻塞用，有新连接时才会结束
			socket_select($this->cycle, $write, $except, null);
			foreach ($this->cycle as $k => $v) {
				if($v === $this->socket) {
					if (($accept = socket_accept($v)) < 0) {
						continue;
					}
					//如果请求来自监听端口那个套接字，则创建一个新的套接字用于通信
					$this->add_accept($accept);
					continue;
				}
				$index = array_search($v, $this->accept);
				if ($index === NULL) {
					continue;
				}
				if (!@socket_recv($v, $data, 10240, 0) || !$data) {//没消息的socket就跳过
					$this->close($v);
					continue;
				}
				if (!$this->isHand[$index]) {
					$this->upgrade($v, $data, $index);
//					if(!empty($this->function['add'])) {
//						call_user_func_array($this->function['add'], array($this));
//					}
					continue;
				}
				$data = $this->decode($data);
                //自定义json字段
				$this->_handle($data, $index);
//				if(!empty($this->function['send'])) {
//					call_user_func_array($this->function['send'], array($data, $index, $this));
//				}
			}
//			sleep(1);
		}
	}
	//增加一个初次连接的用户
	private function add_accept($accept) {
		$this->accept[] = $accept;
		$index = array_keys($this->accept);
		$index = end($index);
		$this->isHand[$index] = FALSE;

		//
        $id = $index;
        self::$connections[$id] = $accept;
        //self::$buffers[$id] = $buffer;
        self::$heartbeats[$id] = 0;
        self::$uid[$id] = 0;
	}
	//关闭一个连接
	private function close($accept) {
		$index = array_search($accept, $this->accept);
		socket_close($accept);
		unset($this->accept[$index]);
		unset($this->isHand[$index]);
//		if(!empty($this->function['close'])) {
//			call_user_func_array($this->function['close'], array($this));
//		}

        $id = $index;
        unset(self::$connections[$id], self::$chats[$id], self::$auth[$id], self::$heartbeats[$id], self::$uid[$id]);
	}
	//响应升级协议
	private function upgrade($accept, $data, $index) {
		if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$data,$match)) {
			$key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
			$upgrade  = "HTTP/1.1 101 Switching Protocol\r\n" .
					"Upgrade: websocket\r\n" .
					"Connection: Upgrade\r\n" .
					"Sec-WebSocket-Accept: " . $key . "\r\n\r\n";  //必须以两个回车结尾
			socket_write($accept, $upgrade, strlen($upgrade));
			$this->isHand[$index] = TRUE;
		}
	}

	private function _handle($data, $index){
        $id = $index;
        $messageArr = json_decode($data, true);
        $auth = isset($messageArr['auth'])?$messageArr['auth']:0;
        $topicId = isset($messageArr['topicId'])?$messageArr['topicId']:-1;
        $msgType = isset($messageArr['msgType'])?$messageArr['msgType']:0;
        $chatId = isset($messageArr['chatId'])?$messageArr['chatId']:'';
        $liveStreamStatus = isset($messageArr['liveStreamStatus'])?$messageArr['liveStreamStatus']:1;
        $uid = isset($messageArr['uid'])?$messageArr['uid']:0;

        if ($msgType == 0 || $msgType == 7 || $msgType == 9) {
            self::$chats[$id] = $topicId;
        }
        if($msgType == 0){
            self::$auth[$id] = $auth;
        }
        $data = array();
        $send_ready = false;
        //msgType：0人数统计 1文字 2点赞 3图文 4语音 5删除评论 6审核评论通过 7视频视频流状态 8上墙广播
        //9聊天禁言切换
        //silent：0助理发的，按钮消失 1游客发的，出现禁言按钮 2游客发的，出现恢复禁言按钮
        //status：0待审核，出现绿色√ 1已通过，出现灰色√ 3已通过，助理发的
        switch ($msgType){
            case 0:
                $num = $socket_num = 0;
                foreach (self::$connections as $key => $conn) {
                    if ($topicId == @self::$chats[$key]) {
                        $socket_num++;
                    }
                }
                $res = $this->curlGet("http://esm.zbxinmeiti.com/Wap/Live/online?topic_id=".$topicId);
                $res = json_decode($res,true);
                if($res['status'] == 1){
                    $send_ready = true;
                    $num = intval($res['popularity_num']) + $num;
                    $data = json_encode(array('msgType' => $msgType, 'msg' => $num, 'socket_num' => $socket_num));
                    $data = $this->frame($data);
                }
                break;
            case 1:     //按用户身份广播弹幕内容
                $res = $this->curlGet("http://esm.zbxinmeiti.com/Wap/Live/getOneComment?chat_id=".$chatId);
                $res = json_decode($res,true);
                if($res["status"] == 1){
                    $send_ready = true;
                    foreach (self::$connections as $key => $conn) {
                        //观众发消息
                        if($res['support_comment_check'] == 1){        //评论需要审核，只给助理发
                            if($topicId != @self::$chats[$key]){
                                continue;
                            }else{
                                if(self::$auth[$key] == 1){     //websocket连接对象是助理
                                    $data = array('msgType' => $msgType, 'chatId' => $chatId,
                                        'nickname' => $res['nickname'], 'date' => $res['date'],
                                        'msg' => $res['msg'], 'silent' => 1, 'status' => 0, 'ask' => $res['ask']);
                                    $data = $this->frame(json_encode($data));
                                    //fwrite($conn, $data);
                                    socket_write($conn, $data, strlen($data));
                                }
                            }
                        }else{          //评论不需要审核，助理和观众都发
                            if($topicId != @self::$chats[$key]){
                                continue;
                            }else{
                                if(self::$auth[$key] == 1){     //websocket连接对象是助理
                                    $data = array('msgType' => $msgType, 'chatId' => $chatId,
                                        'nickname' => $res['nickname'], 'date' => $res['date'],
                                        'msg' => $res['msg'], 'silent' => 1, 'status' => 1, 'ask' => $res['ask']);
                                }else{                          //websocket连接对象是观众
                                    $data = array('msgType' => $msgType, 'chatId' => $chatId,
                                        'nickname' => $res['nickname'], 'date' => $res['date'],
                                        'msg' => $res['msg'], 'silent' => 0, 'status' => 1, 'ask' => $res['ask']);
                                }
                                $data = $this->frame(json_encode($data));
                                //fwrite($conn, $data);
                                socket_write($conn, $data, strlen($data));
                            }
                        }
                    }
                }
                break;
            case 2:
                $res = $this->curlGet("http://esm.zbxinmeiti.com/Wap/Live/praise?topic_id=".$topicId);
                $res = json_decode($res, true);
                if($res["status"] == 1){
                    $send_ready = true;
                    $data = json_encode(array('msgType' => $msgType));
                    $data = $this->frame($data);
                }
                break;
            case 3:
                $res = $this->curlGet("http://esm.zbxinmeiti.com/Wap/Live/getOneComment?chat_id=".$chatId);
                $res = json_decode($res,true);
                if($res["status"] == 1){
                    $send_ready = true;
                    $data = json_encode(array('msgType' => $msgType, 'chatId' => $chatId,
                        'nickname' => $res['nickname'],  'date' => $res['date'], 'msg' => $res['msg'],
                        'images' => $res['attach'], 'link' => $res['link']));
                    $data = $this->frame($data);
                }
                break;
            case 4:
                $res = $this->curlGet("http://esm.zbxinmeiti.com/Wap/Live/getOneComment?chat_id=".$chatId);
                $res = json_decode($res,true);
                if($res["status"] == 1){
                    $send_ready = true;
                    $data = json_encode(array('msgType' => $msgType, 'chatId' => $chatId,
                        'nickname' => $res['nickname'], 'date' => $res['date'], 'voice' => $res['attach'],
                        'record_time' => $res['record_time']));
                    $data = $this->frame($data);
                }
                break;
            case 5:
                $send_ready = true;
                $data = json_encode(array('msgType' => $msgType, 'chatId' => $chatId));
                $data = $this->frame($data);
                break;
            case 6:         //按照用户身份广播，自己不广播，其他助理和观众广播，将页面上的待审核状态改变
                $res = $this->curlGet("http://esm.zbxinmeiti.com/Wap/Live/getOneComment?chat_id=".$chatId);
                $res = json_decode($res,true);
                if($res["status"] == 1){
                    $send_ready = true;
                    foreach (self::$connections as $key => $conn) {
                        if($key == $id || @$topicId != self::$chats[$key]){
                            continue;
                        }else{
                            if(self::$auth[$key] == 1){     //websocket连接对象是助理
                                $data = array('msgType' => $msgType, 'chatId' => $chatId, 'msg' => $res['msg'],
                                    'nickname' => $res['nickname'], 'date' => $res['date'], 'silent' => 0,
                                    'status' => 1);
                            }else{      //websocket连接对象是观众
                                $data = array('msgType' => $msgType, 'chatId' => $chatId, 'msg' => $res['msg'],
                                    'nickname' => $res['nickname'], 'date' => $res['date'], 'silent' => 1,
                                    'status' => 1);
                            }
                            $data = $this->frame(json_encode($data));
                            //fwrite($conn, $data);
                            socket_write($conn, $data, strlen($data));
                        }
                    }
                }
                break;
            case 7:
                self::$heartbeats[$id] = 1;
                self::$uid[$id] = $uid;
                $data = json_encode(array('msgType' => $msgType, 'liveStreamStatus' => $liveStreamStatus));
                $data = $this->frame($data);
                if($liveStreamStatus == 3){
                    $send_ready = true;
                    $this->curlGet("http://esm.zbxinmeiti.com/Wap/Live/appEndStream?uid=".self::$uid[$id].
                        "&topic_id=".self::$chats[$id]);
                }
                break;
            case 8:
                $res = $this->curlGet("http://esm.zbxinmeiti.com/Wap/Live/getOneComment?chat_id=".$chatId);
                $res = json_decode($res,true);
                if($res["status"] == 1){
                    $send_ready = true;
                    $data = json_encode(array('msgType' => $msgType, 'chatId' => $chatId,
                        'nickname' => $res['nickname'], 'date' => $res['date'], 'msg' => $res['msg'],
                        'reply' => $res['reply']));
                    $data = $this->frame($data);
                }
                break;
            case 9:     //给游客发送切换消息
                $res = $this->curlGet("http://esm.zbxinmeiti.com/Wap/Live/getSupportCommentAll?topic_id=".$topicId);
                $res = json_decode($res,true);
                if($res["status"] == 1){
                    $send_ready = true;
                    foreach (self::$connections as $key => $conn) {
                        if($topicId != @self::$chats[$key]){
                            continue;
                        }else{
                            if(self::$auth[$key] == 0) {     //websocket连接对象是观众
                                $data = array('msgType' => $msgType, 'support_comment_all' => $res["support_comment_all"]);
                            }else{
                                $data = array('msgType' => $msgType, 'support_comment_all' => 1);
                            }
                            $data = $this->frame(json_encode($data));
                            //fwrite($conn, $data);
                            socket_write($conn, $data, strlen($data));
                        }
                    }
                }
                break;
            default:
                break;
        }
        if($send_ready && $msgType != 1 && $msgType != 6 && $msgType != 9){      //已经根据身份广播过了
            foreach (self::$connections as $key => $conn) {
                //foreach ($this->accept as $key => $conn) {
                if ($topicId != @self::$chats[$key]) {
                    continue;
                } else {
                    socket_write($conn, $data, strlen($data));
                }
            }
        }
    }
    //加密成数据帧
	public function frame($msg)
    {
        $frame = array();
        $frame[0] = '81';
        $len = strlen($msg);
        if($len < 126){
            $frame[1] = $len<16?'0'.dechex($len):dechex($len);
        }else if($len < 65025){
            $s=dechex($len);
            $frame[1]='7e'.str_repeat('0',4-strlen($s)).$s;
        }else{
            $s=dechex($len);
            $frame[1]='7f'.str_repeat('0',16-strlen($s)).$s;
        }
        $frame[2] = $this->ord_hex($msg);
        $data = implode('',$frame);
        return pack("H*", $data);
	}
    private function ord_hex($data)  {
        $msg = '';
        $l = strlen($data);
        for ($i= 0; $i<$l; $i++) {
            $msg .= dechex(ord($data{$i}));
        }
        return $msg;
    }

	//数据解密
	public function decode($buffer) {
		$len = $masks = $data = $decoded = null;
		$len = ord($buffer[1]) & 127;
		if ($len === 126) {
			$masks = substr($buffer, 4, 4);
			$data = substr($buffer, 8);
		}
		else if ($len === 127) {
			$masks = substr($buffer, 10, 4);
			$data = substr($buffer, 14);
		}
		else {
			$masks = substr($buffer, 2, 4);
			$data = substr($buffer, 6);
		}
		for ($index = 0; $index < strlen($data); $index++) {
			$decoded .= $data[$index] ^ $masks[$index % 4];
		}
		return $decoded;
	}

    private function curlGet($url){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 500);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        $out = curl_exec($ch);
        curl_close($ch);
        return $out;
    }
}
